﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Extensions.cs" company="Vlady-mix">
//   Vlady-mix 2015
// </copyright>
// <summary>
//   The extensions.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace CustomDatePicker.Controls.DatePicker.Common
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;

    using Windows.Foundation;
    using Windows.UI.Xaml;
    using Windows.UI.Xaml.Controls;
    using Windows.UI.Xaml.Data;
    using Windows.UI.Xaml.Media;
    using Windows.UI.Xaml.Media.Animation;

    /// <summary>
    /// The extensions.
    /// </summary>
    public static class Extensions
    {
        #region Public Methods

        /// <summary>
        /// Begins a storyboard and waits for it to complete.
        /// </summary>
        /// <param name="storyboard">
        /// The storyboard.
        /// </param>
        /// <returns>
        /// The <see cref="Task"/>.
        /// </returns>
        public static Task RunAsync(this Storyboard storyboard)
        {
            var taskCompletionSource = new TaskCompletionSource<object>();

            EventHandler<object> eventHandler = null;

            eventHandler = (sender, o) =>
            {
                storyboard.Completed -= eventHandler;
                taskCompletionSource.TrySetResult(null);
            };

            storyboard.Completed += eventHandler;

            storyboard.Begin();

            return taskCompletionSource.Task;
        }

        /// <summary>
        /// Subsribe to a Dependency Property Changed Event
        /// </summary>
        /// <param name="element">
        /// The element.
        /// </param>
        /// <param name="property">
        /// The property.
        /// </param>
        /// <param name="propertyChangedCallback">
        /// The property Changed Callback.
        /// </param>
        public static void SubscribePropertyChanged(this FrameworkElement element, string property, PropertyChangedCallback propertyChangedCallback)
        {
            var b = new Binding { Path = new PropertyPath(property), Source = element };
            var prop = DependencyProperty.RegisterAttached(property, typeof(object), typeof(Control), new PropertyMetadata(null, propertyChangedCallback));

            element.SetBinding(prop, b);
        }

        /// <summary>
        /// The get visual ancestor.
        /// </summary>
        /// <param name="d">
        /// The d.
        /// </param>
        /// <param name="type">
        /// The type.
        /// </param>
        /// <returns>
        /// The <see cref="DependencyObject"/>.
        /// </returns>
        public static DependencyObject GetVisualAncestor(this DependencyObject d, Type type)
        {
            DependencyObject item = VisualTreeHelper.GetParent(d);

            while (item != null)
            {
                if (item.GetType() == type)
                {
                    return item;
                }

                item = VisualTreeHelper.GetParent(item);
            }

            return null;
        }

        #endregion

        #region Internal Methods

        /// <summary>
        /// The get closest.
        /// </summary>
        /// <param name="itemsControl">
        /// The items control.
        /// </param>
        /// <param name="items">
        /// The items.
        /// </param>
        /// <param name="position">
        /// The position.
        /// </param>
        /// <param name="searchDirection">
        /// The search direction.
        /// </param>
        /// <param name="selectedItem">
        /// The selected item.
        /// </param>
        /// <param name="searchDown">
        /// The search down.
        /// </param>
        /// <returns>
        /// The <see cref="UIElement"/>.
        /// </returns>
        internal static UIElement GetClosest(
            FrameworkElement itemsControl,
            IEnumerable<DependencyObject> items,
            Point position,
            Orientation searchDirection,
            UIElement selectedItem,
            bool searchDown = true)
        {
            UIElement closest = null;
            double closestDistance = double.MaxValue;

            var arrayItems = items as DependencyObject[] ?? items.ToArray();

            for (int cpt = 0; cpt < arrayItems.Count(); cpt++)
            {
                var uiElement = arrayItems[cpt] as UIElement;

                if (uiElement == null)
                {
                    continue;
                }

                var rect2 = new Rect();
                Rect rect = uiElement.TransformToVisual(itemsControl).TransformBounds(rect2);

                if (position.Y <= rect.Y + uiElement.RenderSize.Height && position.Y >= rect.Y)
                {
                    return uiElement;
                }
            }

            // this code is not used. Just for reminder :)
            if (searchDown)
            {
                for (int cpt = 0; cpt < arrayItems.Count(); cpt++)
                {
                    var uiElement = arrayItems[cpt] as UIElement;

                    if (uiElement != null)
                    {
                        /* if (selectedItem != null &&  uiElement == selectedItem)
                            return uiElement;
                        */
                        Point p = uiElement.TransformToVisual(itemsControl).TransformPoint(new Point(0, 0));

                        // ReSharper disable once UnusedVariable
                        Rect rect = uiElement.TransformToVisual(itemsControl).TransformBounds(new Rect());

                        // Get Distance. Must be neagative
                        double distance = GetDistance(itemsControl, position, p, searchDirection);

                        // if distance is positive, and it's the first item, must select it (we are at the top
                        if (distance > 0 && cpt == 0)
                        {
                            return uiElement;
                        }

                        if (distance > 0)
                        {
                            break;
                        }

                        // Get absolute
                        distance = Math.Abs(distance);

                        if (distance > closestDistance)
                        {
                            continue;
                        }

                        // If the closest item is the item itself (and it's not the first one !)
                        // I can break
                        if (uiElement == selectedItem && cpt > 0)
                        {
                            break;
                        }

                        closest = uiElement;
                        closestDistance = distance;
                    }

                    // if UIElement is null then i scroll uo but im at the first item which is above, so i select it
                    if (closest == null)
                    {
                        closest = arrayItems[0] as UIElement;
                    }
                }
            }
            else
            {
                for (int cpt = arrayItems.Count() - 1; cpt >= 0; cpt--)
                {
                    var uiElement = arrayItems[cpt] as UIElement;

                    if (uiElement != null)
                    {
                        /*if (selectedItem != null && uiElement == selectedItem)
                            return uiElement;
                        */
                        Point p = uiElement.TransformToVisual(itemsControl).TransformPoint(new Point(0, 0));

                        double distance = GetDistance(itemsControl, position, p, searchDirection);

                        if (distance < 0)
                        {
                            continue;
                        }

                        if (!(distance <= closestDistance))
                        {
                            continue;
                        }

                        closest = uiElement;
                        closestDistance = distance;
                    }
                }

                // if UIElement is null then i scroll down but im at the last item which is above, so i select it
                if (closest == null)
                {
                    closest = arrayItems[arrayItems.Count() - 1] as UIElement;
                }
            }

            return closest;
        }

        /// <summary>
        /// The find visual child.
        /// </summary>
        /// <param name="obj">
        /// The obj.
        /// </param>
        /// <typeparam name="T">
        /// The Entity object.
        /// </typeparam>
        /// <returns>
        /// The <see cref="T"/>.
        /// </returns>
        internal static T FindVisualChild<T>(DependencyObject obj) where T : DependencyObject
        {
            // Search immediate children first (breadth-first)   
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
            {
                DependencyObject child = VisualTreeHelper.GetChild(obj, i);

                // ReSharper disable once MergeSequentialChecks
                if (child != null && child is T)
                {
                    return (T)child;
                }

                var childOfChild = FindVisualChild<T>(child);

                if (childOfChild != null)
                {
                    return childOfChild;
                }
            }

            return null;
        }

        /// <summary>
        /// The get visual ancestor.
        /// </summary>
        /// <param name="d">
        /// The d.
        /// </param>
        /// <typeparam name="T">
        /// The Entity object.
        /// </typeparam>
        /// <returns>
        /// The <see cref="T"/>.
        /// </returns>
        internal static T GetVisualAncestor<T>(this DependencyObject d) where T : class
        {
            DependencyObject item = VisualTreeHelper.GetParent(d);

            while (item != null)
            {
                T itemAsT = item as T;
                if (itemAsT != null)
                {
                    return itemAsT;
                }

                item = VisualTreeHelper.GetParent(item);
            }

            return null;
        }

        /// <summary>
        /// The get visual descendent.
        /// </summary>
        /// <param name="d">
        /// The d.
        /// </param>
        /// <typeparam name="T">
        /// The Entity object.
        /// </typeparam>
        /// <returns>
        /// The <see cref="T"/>.
        /// </returns>
        internal static T GetVisualDescendent<T>(this DependencyObject d) where T : DependencyObject
        {
            return d.GetVisualDescendents<T>().FirstOrDefault();
        }

        /// <summary>
        /// The get visual descendents.
        /// </summary>
        /// <param name="d">
        /// The d.
        /// </param>
        /// <typeparam name="T">
        /// The Entity object.
        /// </typeparam>
        /// <returns>
        /// The <see cref="IEnumerable"/>.
        /// </returns>
        internal static IEnumerable<T> GetVisualDescendents<T>(this DependencyObject d) where T : DependencyObject
        {
            int childCount = VisualTreeHelper.GetChildrenCount(d);

            for (int n = 0; n < childCount; n++)
            {
                DependencyObject child = VisualTreeHelper.GetChild(d, n);

                // ReSharper disable once CanBeReplacedWithTryCastAndCheckForNull
                if (child is T)
                {
                    yield return (T)child;
                }

                foreach (T match in GetVisualDescendents<T>(child))
                {
                    yield return match;
                }
            }

            // ReSharper disable once RedundantJumpStatement
            yield break;
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// The get distance.
        /// </summary>
        /// <param name="itemsControl">
        /// The items control.
        /// </param>
        /// <param name="position1">
        /// The position 1.
        /// </param>
        /// <param name="position2">
        /// The position 2.
        /// </param>
        /// <param name="searchDirection">
        /// The search direction.
        /// </param>
        /// <returns>
        /// The <see cref="double"/>.
        /// </returns>
        private static double GetDistance(FrameworkElement itemsControl, Point position1, Point position2, Orientation searchDirection)
        {
            // Point p = uiElement.TransformToVisual(itemsControl).TransformPoint(new Point(0, 0));
            double distance = double.MaxValue;

            switch (searchDirection)
            {
                case Orientation.Horizontal:
                    distance = position2.X - position1.X;
                    break;
                case Orientation.Vertical:
                    distance = position2.Y - position1.Y;
                    break;
            }

            return distance;
        }

        #endregion
    }
}
